package it.eng.get.pdfwe;


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;

import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;
import org.htmlcleaner.CleanerProperties;
import org.htmlcleaner.DomSerializer;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.TagNode;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.tidy.Tidy;
import org.xml.sax.ContentHandler;

/**
 * @author <div style="width: 100%; height: 2em;
 * background-repeat: repeat-x; 
 * background-image: url(http://www.mysoftworks.net/images/author_back.gif);" >
 * <a style="float: right; font-size: .9em; margin: 1.2em .5em .1em .1em; color: #658A16; color: 658A16; font-family: helvetica; font-weight: bold; text-decoration: none" href="http://www.mysoftworks.net">
 * Antonio Giovannetti for Engineering</a>
 * </div>
 */
/**
 * @author <div style="width: 100%; height: 2em;
 * background-repeat: repeat-x; 
 * background-image: url(http://www.mysoftworks.net/images/author_back.gif);" >
 * <a style="float: right; font-size: .9em; margin: 1.2em .5em .1em .1em; color: #658A16; color: 658A16; font-family: helvetica; font-weight: bold; text-decoration: none" href="http://www.mysoftworks.net">
 * Antonio Giovannetti for Engineering</a>
 * </div>
 */
public class FOPHtmlToPdfWriter implements PDFGenerator {

	private String fopCfgFile = "/conf/fop.xconf";
	private String xslLocation = "/xsl/xhtml2fo.xsl";
	public static boolean installedFop;
	private static Class<?> fopFactory = null;
	private static Object fopFactoryInstance = null;
	private static Class<?> dcb = null;
	private static Object dcbInstance;
	private static Class<?> cfg = null;
	
	private boolean useHTMLCleaner=true;
	private Tidy tidyPreprocessor;
	
	public boolean isUseHTMLCleaner() {
		return useHTMLCleaner;
	}

	public void setUseHTMLCleaner(boolean useHTMLCleaner) {
		this.useHTMLCleaner = useHTMLCleaner;
	}


	public Tidy getTidyPreprocessor() {
		return tidyPreprocessor;
	}

	public void setTidyPreprocessor(Tidy tidyPreprocessor) {
		this.tidyPreprocessor = tidyPreprocessor;
	}


	static {
		initFop();
	}
	
	private static void initFop() {
		try {
			fopFactory = Class.forName("org.apache.fop.apps.FopFactory");
			Method m = fopFactory.getMethod("newInstance",(Class[])null);
			fopFactoryInstance = m.invoke(null, (Object[])null); // FopFactory fopFactoryInstance = FopFactory.newInstance();
			setFopUserConfig();
			
			installedFop = true;
		} catch (Exception e) {
			e.printStackTrace();
			installedFop = false;
		}
	}
	
	private static void setFopUserConfig() throws Exception {
		try {
			dcb = Class.forName("org.apache.avalon.framework.configuration.DefaultConfigurationBuilder");
			dcbInstance = dcb.newInstance();
			cfg = Class.forName("org.apache.avalon.framework.configuration.Configuration");
		} catch (Exception e) {
			e.printStackTrace();
			throw new Exception("Libreria Avalon assente o di versione errata",e);
		}
	}
	
	private String rawHtml;
	

	//	private String xslLocation = "/xhtml2fo.xsl"; // // http://localhost:8080/tst/xhtml-to-xslfo.xsl

//	private InputStream xslLocationStream;


	
	public FOPHtmlToPdfWriter(String rawHtml) throws Exception {
		super();
		this.rawHtml = rawHtml;
		//		if (webPath!=null) this.webPath = webPath.replaceFirst("[\\\\/]?$", "/");
		//		if (xslLocation!=null) this.xslLocation = xslLocation.replaceFirst("^[\\\\/]?", "");
		
	}
	
	
	/**
	 * @param rawHtml
	 * @param xslLocation relative to context root; example: /xhtml2fo.xsl 
	 */
//	public HtmlToPdfWriter(String rawHtml,HttpServletRequest request,InputStream xslLocationStream) throws Exception {
//		super();
//		this.rawHtml = rawHtml;
//		//		if (webPath!=null) this.webPath = webPath.replaceFirst("[\\\\/]?$", "/");
//		//		if (xslLocation!=null) this.xslLocation = xslLocation.replaceFirst("^[\\\\/]?", "");
//		this.request = request;
//		this.xslLocationStream = xslLocationStream;
//	}

	//	private void updateImageSrc() {
	//		// http://localhost:8080/tst/image
	//		this.rawHtml = this.rawHtml.replaceAll(java.util.regex.Pattern.quote("src=\"" + JasperPrintWebEditor._IMAGES_URI), "src=\"" + webPath + JasperPrintWebEditor._IMAGES_URI);
	//	}

	private Document getDocumentUsingHtmlCleaner(InputStream input) {
		Document xmlDoc = null;
		
		try {
			ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
			int i=0;
			while ((i=input.read())!=-1) {
				baos2.write(i);
			}
			
			String str = new String(baos2.toByteArray()).replaceAll("&apos;", "'");
			ByteArrayInputStream bais2 = new ByteArrayInputStream(str.getBytes());



			final HtmlCleaner cleaner = new HtmlCleaner();
			CleanerProperties props = cleaner.getProperties();
			props.setTranslateSpecialEntities(true);
			props.setAdvancedXmlEscape(true);
			DomSerializer doms = new DomSerializer(props, true);

			TagNode node = cleaner.clean(bais2); // new StringReader(edt),input new URL("http://localhost:8080/tst/everything.html")
			xmlDoc = doms.createDOM(node);
			
		} catch (Exception e) {
			e.printStackTrace();
		}		
		return xmlDoc;
	}


	private ContentHandler getFopHandler(OutputStream os) throws Exception{
		FopFactory fopFactory = FopFactory.newInstance();
		DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
		InputStream fopCfgFileStream = getClass().getResourceAsStream(fopCfgFile);
		if (fopCfgFileStream==null) throw new Exception("Non posso caricare il file di configurazione di fop " + fopCfgFile);
		Configuration cfg = cfgBuilder.build(fopCfgFileStream);
		fopFactory.setUserConfig(cfg);
//		if (request!=null) fopFactory.setBaseURL("http://localhost:" + request.getLocalPort());
		Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, os);
		return fop.getDefaultHandler();
	}

	
	
	/**
	 * Using reflection
	 * @param os
	 * @return
	 */
	private ContentHandler getFopHandlerUsingReflection(OutputStream os) throws Exception{
		if (!installedFop) throw new Exception("Non posso generare Fop Handler, libreria Fop assente; necessaria Apache FOP 0.94.");
		if (os==null) throw new Exception("OutputStream e' null.");
		try {
//			if (request!=null) {
//				// fopFactory.setBaseURL("http://localhost:" + request.getLocalPort());
//				Method setBaseURLMethod = fopFactory.getMethod("setBaseURL",String.class);
//				String baseUrl = "http://localhost:" + request.getLocalPort();
//				setBaseURLMethod.invoke(fopFactoryInstance, baseUrl);
//			}
			// fopFactory.setUserConfig(cfg);
			Method buildMethod = dcb.getMethod("build", java.io.InputStream.class);
			Object configurationInstance = buildMethod.invoke(dcbInstance, getClass().getResourceAsStream(fopCfgFile));
			Method setUserConfigMethod = fopFactory.getMethod("setUserConfig",cfg);
			System.out.println("configurationInstance:" + configurationInstance.getClass().getName() );
			setUserConfigMethod.invoke(fopFactoryInstance, configurationInstance);
			Method newFopMethod = fopFactory.getMethod("newFop",String.class,OutputStream.class);
			Object fopInstance =newFopMethod.invoke(fopFactoryInstance, "application/pdf",os);
			if (fopInstance!=null) {
				Method getDefaultHandlerMethod = fopInstance.getClass().getMethod("getDefaultHandler",(Class[])null);
				return (ContentHandler)getDefaultHandlerMethod.invoke(fopInstance, (Object[])null);
			} else throw new Exception("Non posso generare Fop Handler.");
			
		} catch (Exception e) {
			e.printStackTrace();
			throw new Exception("Non posso generare Fop Handler, libreria Fop assente o configurata in maniera errata; necessaria Apache FOP 0.94", e);
		}
	}
	
	private byte[] fopTransform(Node doc) throws Exception {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ContentHandler handler = null;

//		handler = getFopHandlerUsingReflection(baos);

		handler = getFopHandler(baos);
		
		TransformerFactory tFactory = TransformerFactory.newInstance();

		DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
		dFactory.setNamespaceAware(true);
		DocumentBuilder dBuilder = dFactory.newDocumentBuilder(); 
		InputStream xslLocationStream = getClass().getResourceAsStream(xslLocation);
		Document xslDoc = dBuilder.parse(xslLocationStream); // http://localhost:8080/tst/xhtml-to-xslfo.xsl   
		DOMSource xslDomSource = new DOMSource(xslDoc);

		Transformer transformer = tFactory.newTransformer(xslDomSource);

		Source src = new DOMSource(doc);
		Result res = new SAXResult(handler);
		
		transformer.transform(src, res);
		return baos.toByteArray();

	}


	public byte[] generatePdf() {
		// updateImageSrc();
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		Document originalDoc = null;
		try {
			if (!useHTMLCleaner) {
				Tidy t2 = new Tidy();
				t2.setMakeClean(true);
				t2.setXHTML(true);
				t2.setXmlSpace(true);
				t2.setDocType("omit");
				t2.setNumEntities(true);
				t2.setTidyMark(false);
				t2.setHideEndTags(false);
				PDFWeUtils.tidyParse(t2, new ByteArrayInputStream(rawHtml.getBytes()), baos);
				ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
				originalDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(bais);
			} else {
				ByteArrayInputStream bais;
				if (tidyPreprocessor!=null) {
					PDFWeUtils.tidyParse(tidyPreprocessor, new ByteArrayInputStream(rawHtml.getBytes()), baos);
					bais=new ByteArrayInputStream(baos.toByteArray());
				} else bais=new ByteArrayInputStream(rawHtml.getBytes());
				originalDoc = getDocumentUsingHtmlCleaner(bais);
				
			}
			
		} catch (Exception e) {
			e.printStackTrace();
			StringWriter sw=new StringWriter();
			PrintWriter pw=new PrintWriter(sw);
			sw.getBuffer().append("Errore. Si dovrebbe usare HTML Cleaner oppure impostare correttamente il parser Tidy.");
			e.printStackTrace(pw);
//			return getPDFError(sw.getBuffer().toString());
			return PDFWeUtils.getPDFError(e);
//			return getPDFError(e.getClass().getName() + " " + e.getMessage() + ". Si prega di usare HTML Cleaner ");
		}

		try {
			Node strippedDoc = originalDoc;
//			strippedDoc = extractBody(originalDoc);
			return fopTransform(strippedDoc);

		} catch (Exception e) {
			e.printStackTrace();
			return PDFWeUtils.getPDFError(e);
//			return getPDFError("Errore nella trasformazione FOP:" + e.getClass().getName() + " " + e.getMessage());
		}
	}

}
